package com.fizzgate.dynamic.event.handle;

import com.alipay.sofa.ark.spi.event.biz.AfterBizStartupEvent;
import com.alipay.sofa.ark.spi.service.event.EventHandler;
import com.alipay.sofa.serverless.common.api.SpringServiceFinder;
import com.fizzgate.config.AggregateRedisConfig;
import com.fizzgate.dynamic.util.NodeBizUtils;
import com.fizzgate.plugin.auth.ApiConfigServiceProperties;
import com.fizzgate.util.JacksonUtils;
import com.fizzgate.util.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.stereotype.Component;
import com.alipay.sofa.ark.spi.model.Biz;
import com.fizzgate.plugin.FizzPluginFilter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Component
public class NodeAfterBizStartEventHandler implements EventHandler<AfterBizStartupEvent> {
    private static final Logger log = LoggerFactory.getLogger(NodeAfterBizStartEventHandler.class);
    @Resource
    private ApiConfigServiceProperties apiConfigServiceProperties;
    @Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE)
    private ReactiveStringRedisTemplate rt;

    private final Map<String, String>  pluginConfigMap  = new HashMap<>(32);
    @Resource
    private ReactiveWebServerApplicationContext applicationContext;
    @Override
    public int getPriority() {
        return 0;
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(NodeAfterBizStartEventHandler.class);


    @Override
    public void handleEvent(AfterBizStartupEvent event) {
        Biz source = event.getSource();
        if (NodeBizUtils.isArkBiz(source)){
            try {
                loadPlugin(source);
                initDynamicPluginConfig();
                loadExtension(source);
            } catch (IllegalStateException e) {
                e.printStackTrace();
            }
        }
    }

    private void loadExtension(Biz source) {
        // 组件自己激活
//        try{
//            Map<String, Object> config = new HashMap<String ,Object>();
//            config.put("name", "req");
//            config.put("type","MYSQL");
//
//            Map<String, Object> properties = new HashMap<String ,Object>();
//            properties.put("url","DD");
//            properties.put("sql","DD");
//            config.put("properties",properties);
//            INode node = NodeFactory.buildNode(config, new FlowContext());
//            System.out.println(node);
//        }catch (Exception e){
//            e.printStackTrace();
//        }
    }

    private void loadPlugin(Biz biz){
        BizFilterPluginCache.init();
        try{
            Map<String, FizzPluginFilter> filters = SpringServiceFinder.listModuleServices(biz.getBizName(),biz.getBizVersion(), FizzPluginFilter.class);
            filters.forEach((name, pluginFilter)->{
                FizzPluginFilter dynamicPluginFilter = null;
                try{
                     dynamicPluginFilter =  (FizzPluginFilter)applicationContext.getBean(name);
                }catch (NoSuchBeanDefinitionException e){
                    // do nothing
                }
                // 动态加载插件
                if (dynamicPluginFilter != null){
                    LOGGER.info("find dynamic id:{}, prepare to remove it", name);
                    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)applicationContext.getBeanFactory();
                    beanFactory.destroySingleton(name);
                }

                BizFilterPluginCache.put(name, dynamicPluginFilter);
                LOGGER.info("dynamic load plugin id : {}",name);

                DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)applicationContext.getBeanFactory();
                beanFactory.registerSingleton(name, pluginFilter);
            });
        } catch (Exception e){
            LOGGER.error("load plugin id:{}:{}, get error:{}",biz.getBizName(),biz.getBizVersion(),e.getMessage());
            e.printStackTrace();
        }
    }

    private Result<?> initDynamicPluginConfig() {

        Result<?> result = Result.succ();
        String key = apiConfigServiceProperties.getFizzPluginConfig();
        Flux<Map.Entry<Object, Object>> plugins = rt.opsForHash().entries(key);
        // TODO:除了filter不同之外，其他与插件加载类方法类似，需要合并
        plugins.filter(e->{
                    String json = (String)e.getValue();
                    HashMap<?, ?> map = JacksonUtils.readValue(json, HashMap.class);
                    String plugin = (String) map.get("plugin");
                    if (BizFilterPluginCache.get(plugin) != null){
                        return true;
                    } else {
                        return false;
                    }
                }).collectList()
                .defaultIfEmpty(Collections.emptyList())
                .flatMap(
                        es -> {
                            if (!es.isEmpty()) {
                                String json = null;
                                try {
                                    for (Map.Entry<Object, Object> e : es) {
                                        json = (String) e.getValue();
                                        HashMap<?, ?> map = JacksonUtils.readValue(json, HashMap.class);
                                        String plugin = (String) map.get("plugin");
                                        String pluginConfig = (String) map.get("fixedConfig");
                                        String currentPluginConfig = pluginConfigMap.get(plugin);
                                        if (currentPluginConfig == null || !currentPluginConfig.equals(pluginConfig)) {
                                            if (applicationContext.containsBean(plugin)) {
                                                FizzPluginFilter pluginFilter = (FizzPluginFilter) applicationContext.getBean(plugin);
                                                pluginFilter.init(pluginConfig);
                                                pluginConfigMap.put(plugin, pluginConfig);
                                                log.info("init dynamic plugin {} with {}", plugin, pluginConfig);
                                            } else {
                                                log.warn("no {} bean", plugin);
                                            }
                                        }
                                    }
                                } catch (Throwable t) {
                                    result.code = Result.FAIL;
                                    result.msg  = "init dynamic plugin error, config: " + json;
                                    result.t    = t;
                                }
                            } else {
                                log.info("no dynamic plugin init");
                            }
                            return Mono.empty();
                        }
                )
                .onErrorReturn(
                        throwable -> {
                            result.code = Result.FAIL;
                            result.msg  = "init dynamic plugin error";
                            result.t    = throwable;
                            return true;
                        },
                        result
                )
                .block();
        return result;
    }

    public <T> T registerBean(String name, Class<T> clazz, BeanDefinitionBuilder beanDefinitionBuilder) {
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
        beanFactory.registerBeanDefinition(name, beanDefinition);
        return applicationContext.getBean(name, clazz);
    }



}